"""
Django models for storing Yubikeys
"""

from datetime import datetime
import random

from django.db import models, transaction
from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _
from django.conf import settings

from yubikey import decrypt

def generate_aes_key():
    return ''.join(random.choice('0123456789abcdef') for x in xrange(0,32))

def generate_public_id():
    return ''.join(random.choice('cbdefghijklnrtuv') for x in xrange(0,12))

class Yubikey(models.Model):
    user = models.ForeignKey(User, verbose_name=_('user'))
    public_id = models.CharField(_('public ID'), max_length=12, default=generate_public_id, help_text=_('ModHex value of 12 characters (ModHex only consists of the characters: "cbdefghijklnrtuv".'), unique=True)
    aes_key = models.CharField(_('AES key'), max_length=32, default=generate_aes_key, help_text=_('Hexadecimal value of 32 characters.'))
    secret_id = models.CharField(_('secret ID'), max_length=12, editable=False, blank=True, help_text=_('Hexadecimal value of 12 characters, when empty it will be filled on first use of the key.'))
    is_active = models.BooleanField(_('is active'), default=True, help_text=_('When disabling a key, please give a reason in the remarks field.'))
    remarks = models.TextField(_('remarks'), blank=True)
    counter = models.PositiveIntegerField(_('counter'), default=0, editable=False, help_text='Last value used by the internal counter (0-65535).')
    counter_session = models.PositiveIntegerField(_('session counter'), default=0, editable=False, help_text='Last value used by the internal session counter (0-256).')
    created_at = models.DateTimeField(_('created at'), default=datetime.now, editable=False)
    modified_at = models.DateTimeField(_('modified at'), default=datetime.now, editable=False)

    class Meta:
        verbose_name = _('yubikey')
        verbose_name_plural = _('yubikeys')

    def __unicode__(self):
        return 'Yubikey "%s" for user %s' % (self.public_id, self.user)

    def save(self):
        self.modified_at = datetime.now()
        super(Yubikey, self).save()

    @transaction.commit_manually
    def check_token(self, token):
        """
        Returns a boolean of whether the provided token is correct. It decrypts
        the token, checks the CRC, checks if the counter(s) are higher then the 
        saved one. Fills the secret ID on first use.
        
        It does NOT check the password.

        When succesful it saves the new counter value to protect against using
        the same token again.

        The transaction is commited manually so exceptions after this check
        will not rollback the new counter values.
        """
        yubikey = decrypt.YubikeyToken(token, self.aes_key)
        if yubikey.crc_ok and ((yubikey.counter > self.counter) or (yubikey.counter == self.counter and yubikey.counter_session > self.counter_session)) and (not self.secret_id or yubikey.secret_id == self.secret_id) and yubikey.public_id == self.public_id:
            if not self.secret_id:
                self.secret_id = yubikey.secret_id
            self.counter = yubikey.counter
            self.counter_session = yubikey.counter_session
            self.save()
            transaction.commit()
            return True
        else:
            return False
